<!DOCTYPE html>
<html lang="zh-CN">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <!-- 仿照vue的流程 -->
    <div id="app">
      <input type="text" v-model="msg" />
      <h3>{{msg}}</h3>
    </div>
    <script>
      function Vue(options = {}) {
        this.$el = document.querySelector(options.el) // 挂载Dom的el
        this.$data = options.data
        // 监听订阅池进行保存订阅器
        this._watcherTpl = {}
        // 设置observer 数据监听
        this.observer(this.$data)
        // 编译模板和指令
        this._compile(this.$el)
      }

      Vue.prototype.observer = function (obj) {
        // 对每一个属性进行订阅
        Object.keys(obj).forEach((key) => {
          // 使用watcher绑定属性与页面的关系
          // 添加每一属性的订阅器的存放位置
          this._watcherTpl[key] = {
            _directives: [],
          }
          // 方便调用
          const watcher = this._watcherTpl[key]
          let value = obj[key]
          // 注意，将obj变成this,将变量暴露出来
          Object.defineProperty(obj, key, {
            configurable: true,
            enumerable: true,
            get() {
              console.log(`${key}获取了${value}`)
              return value
            },
            set(newvalue) {
              // 不相等的情况下进行获取
              if (newvalue !== value) {
                value = newvalue
                // 数据变动，通知订阅者
                console.log('数据变动，通知订阅者')
                watcher._directives.forEach((item) => {
                  item.update()
                })
              }
            },
          })
        })
      }

      // Watcher 是compile模板编译生成的，对应的一个个的
      function Watcher(el, vm, val, atrr) {
        this.el = el // 监听的DOM 对象
        this.vm = vm // data数据
        this.val = val // v-model绑定的值
        this.atrr = atrr // 监听的DOM对象的哪个属性，对于input默认的是value
        this.update() // 首次更新
      }

      Watcher.prototype.update = function () {
        // input.value=app.data.msg
        console.log(this.vm.$data[this.val])
        this.el[this.atrr] = this.vm.$data[this.val]
      }

      // 模板编译的过程
      Vue.prototype._compile = function (el) {
        const nodes = el.children
        console.dir(nodes)
        for (let i = 0; i < nodes.length; i++) {
          const node = nodes[i]
          if (node.children.length) {
            this._compile(node)
          }

          // 判断是否是v-model ,是则进行双向的数据绑定
          if (node.hasAttributes('v-model') && node.tagName === 'INPUT') {
            node.addEventListener(
              'input',
              (() => {
                // this.$data[attVal] = node.value
                const attVal = node.getAttribute('v-model') // msg
                // 创建watcher对象，并将订阅器根据属性放入到对应的集合中
                const watcher = new Watcher(node, this, attVal, 'value') // 根据节点生成的watcher
                this._watcherTpl[attVal]._directives.push(watcher)
                console.log(this)
                return () => {
                  this.$data[attVal] = node.value // this.$data[attVal]=nodes[key].value
                }
              })()
            )
          }

          // {{}}的模板匹配
          const txt = node.textContent
          const reg = /{{\s*(\S+)\s*}}/
          const resultArr = reg.exec(txt)
          if (resultArr) {
            // node.textContent = resultArr[1]
            const watcher = new Watcher(node, this, resultArr[1], 'innerHTML')
            this._watcherTpl[resultArr[1]]._directives.push(watcher)
          }
          console.log(node)
        }
      }
    </script>
    <script>
      var app = new Vue({
        el: '#app',
        data: {
          msg: 'helloWorld',
          age: 18,
        },
      })
    </script>
  </body>
</html>
